/*-------------------------------------------------------------------------
 *
 * controldata_utils.c
 *		控制数据文件输出的常用代码。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/controldata_utils.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "common/controldata_utils.h"
#include "common/file_perm.h"
#ifdef FRONTEND
#include "common/logging.h"
#endif
#include "port/pg_crc32c.h"

#ifndef FRONTEND
#include "pgstat.h"
#include "storage/fd.h"
#endif

/*
 * get_controlfile()
 *
 * 获取控制文件的值。结果以 palloc 的方式返回控制文件数据的副本。
 *
 * crc_ok_p 可供调用者使用，以查看控制文件数据的 CRC 是否正确。
 */
ControlFileData *
get_controlfile(const char *fc_DataDir, bool *fc_crc_ok_p)
{
	ControlFileData *fc_ControlFile;
	int			fc_fd;
	char		fc_ControlFilePath[MAXPGPATH];
	pg_crc32c	fc_crc;
	int			fc_r;
#ifdef FRONTEND
	pg_crc32c	fc_last_crc;
	int			fc_retries = 0;
#endif

	AssertArg(fc_crc_ok_p);

	fc_ControlFile = palloc(sizeof(ControlFileData));
	snprintf(fc_ControlFilePath, MAXPGPATH, "%s/global/pg_control", fc_DataDir);

#ifdef FRONTEND
	INIT_CRC32C(fc_last_crc);

retry:
#endif

#ifndef FRONTEND
	if ((fc_fd = OpenTransientFile(fc_ControlFilePath,O_RDONLY | PG_BINARY)) == -1)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\" for reading: %m",
						fc_ControlFilePath)));
#else
	if ((fc_fd = open(fc_ControlFilePath, O_RDONLY | PG_BINARY, 0)) == -1)
		pg_fatal("could not open file \"%s\" for reading: %m",
				 fc_ControlFilePath);
#endif

	fc_r = read(fc_fd, fc_ControlFile, sizeof(ControlFileData));
	if (fc_r != sizeof(ControlFileData))
	{
		if (fc_r < 0)
#ifndef FRONTEND
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read file \"%s\": %m", fc_ControlFilePath)));
#else
			pg_fatal("could not read file \"%s\": %m", fc_ControlFilePath);
#endif
		else
#ifndef FRONTEND
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("could not read file \"%s\": read %d of %zu",
							fc_ControlFilePath, fc_r, sizeof(ControlFileData))));
#else
			pg_fatal("could not read file \"%s\": read %d of %zu",
					 fc_ControlFilePath, fc_r, sizeof(ControlFileData));
#endif
	}

#ifndef FRONTEND
	if (CloseTransientFile(fc_fd) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m",
						fc_ControlFilePath)));
#else
	if (close(fc_fd) != 0)
		pg_fatal("could not close file \"%s\": %m", fc_ControlFilePath);
#endif

	/* 检查 CRC。 */
	INIT_CRC32C(fc_crc);
	COMP_CRC32C(fc_crc,
				(char *) fc_ControlFile,
				offsetof(ControlFileData, crc));
	FIN_CRC32C(fc_crc);

	*fc_crc_ok_p = EQ_CRC32C(fc_crc, fc_ControlFile->crc);

#ifdef FRONTEND

	/*
	 * 如果服务器同时在写入，某些系统上可能会读取到部分更新的内容。
	 * 如果 CRC 不匹配，则重新尝试有限次数，直到我们计算出连续两次相同的坏 CRC，
	 * 中间短暂休眠。此时失败不太可能是由于并发写入造成的。
	 */
	if (!*fc_crc_ok_p &&
		(fc_retries == 0 || !EQ_CRC32C(fc_crc, fc_last_crc)) &&
		fc_retries < 10)
	{
		fc_retries++;
		fc_last_crc = fc_crc;
		pg_usleep(10000);
		goto retry;
	}
#endif

	/* 确保控制文件的字节顺序有效。 */
	if (fc_ControlFile->pg_control_version % 65536 == 0 &&
		fc_ControlFile->pg_control_version / 65536 != 0)
#ifndef FRONTEND
		elog(ERROR, _("byte ordering mismatch"));
#else
		pg_log_warning("possible byte ordering mismatch\n"
					   "The byte ordering used to store the pg_control file might not match the one\n"
					   "used by this program.  In that case the results below would be incorrect, and\n"
					   "the PostgreSQL installation would be incompatible with this data directory.");
#endif

	return fc_ControlFile;
}

/*
 * update_controlfile()
 *
 * 使用调用者给出的内容更新控制文件的值。要写入的内容
 * 包含在 "ControlFile" 中。"do_sync" 可选地用于刷新更新后的控制文件。
 * 注意，在后端调用此例程时，恰当地锁住 ControlFileLock 由调用者负责。
 */
void update_controlfile(const char *fc_DataDir,
				   ControlFileData *fc_ControlFile, bool fc_do_sync)
{
	int			fc_fd;
	char		fc_buffer[PG_CONTROL_FILE_SIZE];
	char		fc_ControlFilePath[MAXPGPATH];

	/*
	 * 应用与后端的 WriteControlFile() 中相同的静态断言。
	 */
	StaticAssertStmt(sizeof(ControlFileData) <= PG_CONTROL_MAX_SAFE_SIZE,
					 "pg_control is too large for atomic disk writes");
	StaticAssertStmt(sizeof(ControlFileData) <= PG_CONTROL_FILE_SIZE,
					 "sizeof(ControlFileData) exceeds PG_CONTROL_FILE_SIZE");

	/* 更新时间戳 */
	fc_ControlFile->time = (pg_time_t) time(NULL);

	/* 重新计算控制文件的 CRC */
	INIT_CRC32C(fc_ControlFile->crc);
	COMP_CRC32C(fc_ControlFile->crc,
				(char *) fc_ControlFile,
				offsetof(ControlFileData, crc));
	FIN_CRC32C(fc_ControlFile->crc);

	/*
	 * 将 PG_CONTROL_FILE_SIZE 字节写入 pg_control，使用零填充
	 * 超出 sizeof(ControlFileData) 的部分，以避免读取时出现过早的 EOF 相关
	 * 错误。
	 */
	memset(fc_buffer, 0, PG_CONTROL_FILE_SIZE);
	memcpy(fc_buffer, fc_ControlFile, sizeof(ControlFileData));

	snprintf(fc_ControlFilePath, sizeof(fc_ControlFilePath), "%s/%s", fc_DataDir, XLOG_CONTROL_FILE);

#ifndef FRONTEND

	/*
	 * 所有错误都发出 PANIC，因此无需使用 OpenTransientFile() 并担心
	 * 文件描述符泄漏。
	 */
	if ((fc_fd = BasicOpenFile(fc_ControlFilePath, O_RDWR | PG_BINARY)) < 0)
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m",
						fc_ControlFilePath)));
#else
	if ((fc_fd = open(fc_ControlFilePath, O_WRONLY | PG_BINARY,
				   pg_file_create_mode)) == -1)
		pg_fatal("could not open file \"%s\": %m", fc_ControlFilePath);
#endif

	errno = 0;
#ifndef FRONTEND
	pgstat_report_wait_start(WAIT_EVENT_CONTROL_FILE_WRITE_UPDATE);
#endif
	if (write(fc_fd, fc_buffer, PG_CONTROL_FILE_SIZE) != PG_CONTROL_FILE_SIZE)
	{
		/* 如果写入未设置 errno，假设问题是没有磁盘空间 */
		if (errno == 0)
			errno = ENOSPC;

#ifndef FRONTEND
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not write file \"%s\": %m",
						fc_ControlFilePath)));
#else
		pg_fatal("could not write file \"%s\": %m", fc_ControlFilePath);
#endif
	}
#ifndef FRONTEND
	pgstat_report_wait_end();
#endif

	if (fc_do_sync)
	{
#ifndef FRONTEND
		pgstat_report_wait_start(WAIT_EVENT_CONTROL_FILE_SYNC_UPDATE);
		if (pg_fsync(fc_fd) != 0)
			ereport(PANIC,
					(errcode_for_file_access(),
					 errmsg("could not fsync file \"%s\": %m",
							fc_ControlFilePath)));
		pgstat_report_wait_end();
#else
		if (fsync(fc_fd) != 0)
			pg_fatal("could not fsync file \"%s\": %m", fc_ControlFilePath);
#endif
	}

	if (close(fc_fd) != 0)
	{
#ifndef FRONTEND
		ereport(PANIC,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m",
						fc_ControlFilePath)));
#else
		pg_fatal("could not close file \"%s\": %m", fc_ControlFilePath);
#endif
	}
}
